NEXUS-AI
═══════════════════════════════════════════════════════════ */}
Espacio publicitario Adsterra Agrega tu código de anuncio aquí
{/* Barra de progreso y contador */}
{!canSkip && (
Continuar en {secondsLeft}s...
)}
); } // ── Main App ───────────────────────────────────────────────────────────── function App() { const [appVisible, setAppVisible] = useState(false); const [user, setUser] = useState(null); const [authChecked, setAuthChecked] = useState(false); const [conversations, setConversations] = useState([]); const [activeConvId, setActiveConvId] = useState(null); const [activeConvTitle, setActiveConvTitle] = useState('Nueva conversación'); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [file, setFile] = useState(null); const [preview, setPreview] = useState(null); const [sidebarOpen, setSidebarOpen] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(null); // ── Image generation states ────────────────────────────────────────── const [showImgPanel, setShowImgPanel] = useState(false); const [imgPrompt, setImgPrompt] = useState(''); const [imgLoading, setImgLoading] = useState(false); const fileInputRef = useRef(); const cameraInputRef = useRef(); const scrollRef = useRef(); const imgPromptRef = useRef(); // ── Splash + Auth check ────────────────────────────────────────────── useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const oauthToken = urlParams.get('token'); const authError = urlParams.get('auth_error'); if (oauthToken) { setToken(oauthToken); fetch('https://alejo2104-nexus-ai.hf.space/api/auth/me', { headers: { Authorization: `Bearer ${oauthToken}` } }) .then(r => r.json()) .then(u => { setUser(u); localStorage.setItem('nexus_user', JSON.stringify(u)); }) .catch(() => {}); window.history.replaceState({}, '', '/'); } setTimeout(() => { const splash = document.getElementById('splash'); if (splash) { splash.style.opacity = '0'; setTimeout(() => { splash.style.display = 'none'; setAppVisible(true); }, 500); } }, 1800); const storedToken = getToken(); const storedUser = getUser(); if (storedToken && storedUser) { setUser(storedUser); fetch('https://alejo2104-nexus-ai.hf.space/api/auth/me', { headers: { Authorization: `Bearer ${storedToken}` } }) .then(r => { if (!r.ok) throw new Error('invalid'); return r.json(); }) .then(u => { setUser(u); localStorage.setItem('nexus_user', JSON.stringify(u)); }) .catch(() => { removeToken(); removeUser(); setUser(null); }); } setAuthChecked(true); }, []); useEffect(() => { if (user) loadConversations(); }, [user]); useEffect(() => { scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' }); }, [messages, loading, imgLoading]); const loadConversations = async () => { try { const res = await authFetch('https://alejo2104-nexus-ai.hf.space/api/conversations'); const data = await res.json(); setConversations(data.conversations || []); } catch (e) {} }; const handleAuth = (userData) => setUser(userData); const handleLogout = () => { removeToken(); removeUser(); setUser(null); setConversations([]); setActiveConvId(null); setMessages([]); }; const handleNewConv = async () => { try { const res = await authFetch('https://alejo2104-nexus-ai.hf.space/api/conversations', { method: 'POST' }); const data = await res.json(); setActiveConvId(data.conversation_id); setActiveConvTitle('Nueva conversación'); setMessages([]); setSidebarOpen(false); } catch (e) {} }; const handleSelectConv = async (convId, title) => { setActiveConvId(convId); setActiveConvTitle(title || 'Conversación'); setMessages([]); setShowImgPanel(false); try { const res = await authFetch(`/api/conversations/${convId}/messages`); const data = await res.json(); setMessages((data.messages || []).map(m => ({ ...m, id: Math.random() }))); } catch (e) {} }; const handleDeleteConv = (convId) => setShowDeleteModal(convId); const confirmDelete = async () => { const convId = showDeleteModal; setShowDeleteModal(null); try { await authFetch(`/api/conversations/${convId}`, { method: 'DELETE' }); setConversations(prev => prev.filter(c => (c._id || c.id) !== convId)); if (activeConvId === convId) { setActiveConvId(null); setMessages([]); } } catch (e) {} }; const handleFileChange = (e) => { const selected = e.target.files[0]; if (selected) { setFile(selected); if (selected.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = (e) => setPreview({ type: 'image', data: e.target.result }); reader.readAsDataURL(selected); } else { setPreview({ type: 'file', name: selected.name }); } } }; const sendMessage = async () => { if ((!input.trim() && !file) || loading) return; let convId = activeConvId; if (!convId) { try { const res = await authFetch('https://alejo2104-nexus-ai.hf.space/api/conversations', { method: 'POST' }); const data = await res.json(); convId = data.conversation_id; setActiveConvId(convId); setActiveConvTitle('Nueva conversación'); } catch (e) { return; } } const currentInput = input; const currentFile = file; const currentPreview = preview; const userMsg = { role: 'user', content: currentInput, id: Date.now(), file_info: currentFile ? { name: currentFile.name, type: currentFile.type.startsWith('image/') ? 'image' : 'file', data: currentPreview?.data } : null }; setMessages(prev => [...prev, userMsg]); setInput(''); setFile(null); setPreview(null); setLoading(true); const formData = new FormData(); formData.append('message', currentInput); formData.append('conversation_id', convId); formData.append('user_id', user?.id || ''); if (currentFile) formData.append('file', currentFile); const token = getToken(); try { const response = await fetch('https://alejo2104-nexus-ai.hf.space/api/chat/full', { method: 'POST', body: formData, headers: token ? { Authorization: `Bearer ${token}` } : {} }); const data = await response.json(); if (data.response) { setMessages(prev => [...prev, { role: 'assistant', content: data.response, id: Date.now() + 1 }]); loadConversations(); } else if (data.error) { setMessages(prev => [...prev, { role: 'assistant', content: '⚠️ Error: ' + data.error, id: Date.now() + 1 }]); } } catch (err) { setMessages(prev => [...prev, { role: 'assistant', content: '⚠️ Error de conexión con el servidor.', id: Date.now() + 1 }]); } finally { setLoading(false); } }; // ── Image Generation Logic ─────────────────────────────────────────── // Paso 1: usuario hace clic en "Generar imagen" → se muestra el panel const handleOpenImgPanel = () => { setShowImgPanel(true); setTimeout(() => imgPromptRef.current?.focus(), 100); }; // Paso 2: usuario escribe el prompt y hace clic en "Generar" // → se muestra el anuncio Adsterra primero const handleRequestImage = () => { if (!imgPrompt.trim() || imgLoading) return; const prompt = imgPrompt.trim(); setImgPrompt(''); setShowImgPanel(false); generateImage(prompt); }; // Paso 3: el usuario cierra el anuncio → se genera la imagen const generateImage = async (prompt) => { // Asegurarse de tener una conversación activa let convId = activeConvId; if (!convId) { try { const res = await authFetch('https://alejo2104-nexus-ai.hf.space/api/conversations', { method: 'POST' }); const data = await res.json(); convId = data.conversation_id; setActiveConvId(convId); setActiveConvTitle('Nueva conversación'); } catch (e) {} } // Agregar mensaje del usuario al chat const userMsg = { role: 'user', content: `🎨 Generar imagen: "${prompt}"`, id: Date.now(), is_image_request: true }; setMessages(prev => [...prev, userMsg]); setImgLoading(true); try { const token = getToken(); const response = await fetch('https://alejo2104-nexus-ai.hf.space/api/generate-image', { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify({ prompt: prompt, conversation_id: convId || '', user_id: user?.id || '' }) }); const data = await response.json(); if (data.image) { setMessages(prev => [...prev, { role: 'assistant', content: '', id: Date.now() + 1, generated_image: data.image, image_prompt: prompt }]); if (convId) loadConversations(); } else if (data.error) { setMessages(prev => [...prev, { role: 'assistant', content: `⚠️ ${data.error}`, id: Date.now() + 1 }]); } } catch (err) { setMessages(prev => [...prev, { role: 'assistant', content: '⚠️ Error al generar la imagen. Verifica tu conexión e intenta de nuevo.', id: Date.now() + 1 }]); } finally { setImgLoading(false); } }; // Descargar imagen generada const downloadImage = (dataUrl, prompt) => { const a = document.createElement('a'); a.href = dataUrl; a.download = `nexus-ai-${prompt.slice(0,30).replace(/\s+/g,'-')}.png`; a.click(); }; // ── Render ─────────────────────────────────────────────────────────── if (!authChecked) return null; if (!user) { return (
); } return (
{/* ── Delete Confirmation Modal ── */} {showDeleteModal && (
setShowDeleteModal(null)}>
e.stopPropagation()}>
¿Eliminar conversación?

Esta acción no se puede deshacer.

)} {/* ── Mobile sidebar toggle ── */} {/* ── Sidebar ── */} setSidebarOpen(false)} /> {/* ── Main Chat Area ── */}
{activeConvTitle}
{/* Botón de generación de imágenes en el header */}
{/* ── Image Generation Panel ── */} {showImgPanel && (
Generar imagen con FLUX.1 ✦ FLUX.1-schnell
setImgPrompt(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleRequestImage()} disabled={imgLoading} />
💡 Se mostrará un breve anuncio antes de generar. Apoya a NEXUS-AI viendo anuncios.
)} {/* ── Messages / Welcome ── */} {messages.length === 0 && !loading && !imgLoading ? (

¿Cómo puedo ayudarte hoy?

Chatea, sube imágenes, PDFs o genera imágenes con IA.

) : (
{messages.map(m => (
{m.role === 'user' ? (user?.name ? user.name[0].toUpperCase() : 'U') : ''}
{/* Imagen subida por el usuario */} {m.file_info && m.file_info.type === 'image' && ( Subida )} {m.file_info && m.file_info.type !== 'image' && (
📄 {m.file_info.name}
)} {/* Imagen generada por FLUX.1 */} {m.generated_image && (
✦ Generado con FLUX.1-schnell
{m.image_prompt && (
"{m.image_prompt}"
)} {m.image_prompt downloadImage(m.generated_image, m.image_prompt || 'nexus-ai')} title="Clic para descargar" />
)} {/* Texto del mensaje */} {m.content && (
)}
))} {/* Loading del chat normal */} {loading && (
Generando respuesta ...
)} {/* Loading de generación de imagen */} {imgLoading && (
Generando imagen con FLUX.1... Esto puede tardar 15-30 segundos
)}
)} {/* ── Input Bar ── */}
{preview && (
{preview.type === 'image' ? preview : 📄 {preview.name} }
)}